/*
 * The contents of this file are subject to the Mozilla Public License
 * Version 1.1 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
 * License for the specific language governing rights and limitations
 * under the License.
 * 
 * The Original Code is iSQL-Viewer, A Mutli-Platform Database Tool.
 *
 * The Initial Developer of the Original Code is iSQL-Viewer, A Mutli-Platform Database Tool.
 * Portions created by Mark A. Kobold are Copyright (C) 2000-2007. All Rights Reserved.
 *
 * Contributor(s): 
 *  Mark A. Kobold [mkobold <at> isqlviewer <dot> com].
 *  
 * If you didn't download this code from the following link, you should check
 * if you aren't using an obsolete version: http://www.isqlviewer.com
 */
package org.isqlviewer.ui;

import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Frame;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowEvent;
import java.util.Collection;

import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.DefaultListCellRenderer;
import javax.swing.DefaultListModel;
import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JSeparator;
import javax.swing.ListSelectionModel;
import javax.swing.SwingConstants;
import javax.swing.UIManager;
import javax.swing.WindowConstants;
import javax.swing.border.BevelBorder;

import org.isqlviewer.swing.SwingUtilities;
import org.isqlviewer.swing.WizardPanel;
import org.isqlviewer.swing.action.SwingEventManager;
import org.isqlviewer.ui.wizards.Step;
import org.isqlviewer.ui.wizards.Wizard;
import org.isqlviewer.ui.wizards.WizardContext;
import org.isqlviewer.util.LocalMessages;

/**
 * Runtime environment for running wizard sub-systems.
 * <p>
 * This dialog window follows the wizard interface for showing step by step processes.
 * 
 * @author Mark A. Kobold &lt;mkobold at isqlviewer dot com&gt;
 * @version 1.0
 */
public class WizardRunner extends JDialog implements ActionListener {

    private static final long serialVersionUID = -1030110039402276194L;

    private LocalMessages messages = new LocalMessages("org.isqlviewer.ui.ResourceBundle");

    private static final GridBagConstraints UI_CONSTRAINT = new GridBagConstraints(0, 0, 0, 0, 0, 0, 0, 0, new Insets(
            1, 1, 1, 1), 0, 0);

    private DefaultListModel steplistModel = new DefaultListModel();
    private JList stepList = new JList(steplistModel);
    private JButton nextButton = new JButton();
    private JButton previousButton = new JButton();
    private JButton cancelButton = new JButton();
    private JButton homeButton = new JButton();

    private JLabel marqueeLabel = new JLabel();
    private JLabel commentLabel = new JLabel();
    private JLabel iconLabel = new JLabel();

    private JPanel bannerPanel = new JPanel(new GridBagLayout());
    private JPanel navigationPanel = new JPanel(new GridBagLayout());

    private Wizard wizard = null;
    private WizardContextImpl wizardContext = null;
    private Step currentStep = null;
    private JPanel stepContainer = new JPanel(new WizardPanel.Layout());

    public WizardRunner(Frame owner, Wizard wizard, SwingEventManager eventManager) {

        super(owner, true);
        if (wizard == null) {
            throw new NullPointerException(Wizard.class.getName());
        }
        wizardContext = new WizardContextImpl(eventManager, this);
        this.wizard = wizard;
        initUI();
    }

    public void actionPerformed(ActionEvent event) {

        Object source = event.getSource();
        int index = stepList.getSelectedIndex();
        if (source == nextButton) {
            boolean isValid = false;
            try {
                isValid = currentStep.isValid(wizardContext);
            } catch (Throwable t) {
                wizardContext.showErrorDialog(this, t, "");
            }
            if (isValid) {
                Step next = currentStep.getNext();
                if (next == null) {
                    wizard.finish(wizardContext);
                    dispatchEvent(new WindowEvent(this, WindowEvent.WINDOW_CLOSING));
                } else {
                    showStep(next);
                    stepList.setSelectedIndex(index + 1);
                    currentStep = next;
                }
            } else {
                SwingUtilities.beep();
            }
        } else if (source == previousButton) {
            Step previous = currentStep.getPrevious();
            if (previous == null) {
                wizard.finish(wizardContext);
            } else {
                stepList.setSelectedIndex(index - 1);
                showStep(previous);
                currentStep = previous;
            }
        } else if (source == homeButton) {
            Step first = wizard.firstStep();
            if (first == null) {
                wizard.finish(wizardContext);
            } else {
                showStep(first);
                currentStep = first;
            }
        } else if (source == cancelButton) {
            wizard.finish(wizardContext);
            dispatchEvent(new WindowEvent(this, WindowEvent.WINDOW_CLOSING));
        }
    }

    protected void setBanner(String text) {

        marqueeLabel.setText(text);
    }

    protected void setComment(String text) {

        commentLabel.setText(text);
    }

    protected void setIcon(Icon ico) {

        iconLabel.setIcon(ico);
    }

    protected void startWizard() {

        currentStep = wizard.firstStep();
        setTitle(wizard.getTitle());
        steplistModel.addElement(currentStep);
        Step current = currentStep;
        while ((current = current.getNext()) != null) {
            steplistModel.addElement(current);
        }
        stepList.setSelectedIndex(0);
        showStep(currentStep);
    }

    protected synchronized void showStep(Step step) {

        if (step == null) {
            return;
        }
        setBanner(step.getTitle());
        setComment(step.getComment());
        setIcon(step.getImage());
        previousButton.setEnabled(!step.isFirst());
        if (step.isLast()) {
            nextButton.setText(messages.getMessage("wizardrunner.nav_finish.text"));
            nextButton.setToolTipText(messages.getMessage("wizardrunner.nav_finish.tip"));
        } else {
            nextButton.setText(messages.getMessage("wizardrunner.nav_next.text"));
            nextButton.setToolTipText(messages.getMessage("wizardrunner.nav_next.tip"));
        }
        stepContainer.removeAll();
        try {
            step.activate(wizardContext);
        } catch (Exception error) {
            error.printStackTrace();
        }
        stepContainer.add(step.getView());
        validateTree();
        if (!SwingUtilities.isFrameModified(this).booleanValue()) {
            SwingUtilities.setFrameModified(this, Boolean.TRUE);
        }
        repaint();
    }

    protected void setNextStepEnabled(boolean f) {

        nextButton.setEnabled(f);
    }

    protected void setPreviousStepEnabled(boolean f) {

        previousButton.setEnabled(f);
    }

    protected void setHomeAllowable(boolean f) {

        homeButton.setVisible(f);
        homeButton.setEnabled(f);
    }

    @Override
    protected void processWindowEvent(WindowEvent e) {

        switch (e.getID()) {
            case WindowEvent.WINDOW_OPENED :
                startWizard();
                break;
        }
        super.processWindowEvent(e);
    }

    private void initUI() {

        setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
        JPanel rootContentPanel = (JPanel) getContentPane();
        rootContentPanel.setLayout(new WizardPanel.Layout());

        JPanel cp = new JPanel(new GridBagLayout());
        rootContentPanel.add(cp);

        configureToolbar();
        configureBanner();

        stepList.setBorder(BorderFactory.createBevelBorder(BevelBorder.LOWERED));
        stepList.setEnabled(false);
        stepList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        stepList.setCellRenderer(new StepListCellRenderer());

        constrain(0, 0, 1, 3, 0.25, 1.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH);
        cp.add(stepList, UI_CONSTRAINT);

        constrain(1, 0, 1, 3, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH);
        cp.add(Box.createHorizontalStrut(5), UI_CONSTRAINT);

        constrain(2, 0, 1, 1, 1.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL);
        cp.add(bannerPanel, UI_CONSTRAINT);

        constrain(2, 1, 1, 1, 1.0, 1.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH);
        cp.add(stepContainer, UI_CONSTRAINT);

        constrain(2, 2, 1, 1, 1.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL);
        cp.add(navigationPanel, UI_CONSTRAINT);
        wizard.init(wizardContext);

        setSize(new Dimension(620, 360));
        setPreferredSize(getSize());
    }

    private void configureToolbar() {

        homeButton.addActionListener(this);
        previousButton.addActionListener(this);
        nextButton.addActionListener(this);
        cancelButton.addActionListener(this);

        homeButton.setText(messages.getMessage("wizardrunner.nav_home.text"));
        homeButton.setToolTipText(messages.getMessage("wizardrunner.nav_home.tip"));
        homeButton.setVisible(wizard.supportsHome());

        previousButton.setToolTipText(messages.getMessage("wizardrunner.nav_previous.tip"));
        previousButton.setText(messages.getMessage("wizardrunner.nav_previous.text"));

        nextButton.setText(messages.getMessage("wizardrunner.nav_next.text"));
        nextButton.setToolTipText(messages.getMessage("wizardrunner.nav_next.tip"));

        cancelButton.setText(messages.getMessage("wizardrunner.nav_cancel.text"));
        cancelButton.setToolTipText(messages.getMessage("wizardrunner.nav_cancel.tip"));

        constrain(0, 0, 5, 1, 1.0d, 0.0d, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL);
        navigationPanel.add(new JSeparator(SwingConstants.HORIZONTAL), UI_CONSTRAINT);

        constrain(0, 1, 1, 1, 1.0d, 0.0d, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL);
        navigationPanel.add(Box.createHorizontalGlue(), UI_CONSTRAINT);
        constrain(1, 1, 1, 1, 0.0d, 0.0d, GridBagConstraints.EAST, GridBagConstraints.NONE);
        navigationPanel.add(homeButton, UI_CONSTRAINT);
        constrain(2, 1, 1, 1, 0.0d, 0.0d, GridBagConstraints.EAST, GridBagConstraints.NONE);
        navigationPanel.add(previousButton, UI_CONSTRAINT);
        constrain(3, 1, 1, 1, 0.0d, 0.0d, GridBagConstraints.EAST, GridBagConstraints.NONE);
        navigationPanel.add(nextButton, UI_CONSTRAINT);
        constrain(4, 1, 1, 1, 0.0d, 0.0d, GridBagConstraints.EAST, GridBagConstraints.NONE);
        navigationPanel.add(cancelButton, UI_CONSTRAINT);
    }

    private void configureBanner() {

        bannerPanel.setBorder(BorderFactory.createLineBorder(UIManager.getColor("TextArea.foreground"), 1));
        bannerPanel.setBackground(UIManager.getColor("TextArea.background"));
        bannerPanel.setForeground(UIManager.getColor("TextArea.foreground"));
        marqueeLabel.setFont(marqueeLabel.getFont().deriveFont(Font.BOLD, 16f));
        marqueeLabel.setForeground(bannerPanel.getForeground());
        marqueeLabel.setBackground(bannerPanel.getBackground());
        commentLabel.setFont(commentLabel.getFont().deriveFont(Font.ITALIC, 12f));
        commentLabel.setForeground(bannerPanel.getForeground());
        commentLabel.setBackground(bannerPanel.getBackground());

        constrain(0, 0, 1, 2, 0.0d, 0.0d, GridBagConstraints.CENTER, GridBagConstraints.NONE);
        bannerPanel.add(Box.createHorizontalStrut(12), UI_CONSTRAINT);
        constrain(0, 0, 2, 1, 1.0d, 0.0d, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL);
        bannerPanel.add(marqueeLabel, UI_CONSTRAINT);
        constrain(1, 1, 1, 1, 1.0d, 0.0d, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL);
        bannerPanel.add(commentLabel, UI_CONSTRAINT);
        constrain(2, 0, 1, 2, 0.0d, 0.0d, GridBagConstraints.CENTER, GridBagConstraints.NONE);
        bannerPanel.add(iconLabel, UI_CONSTRAINT);
        constrain(3, 0, 1, 2, 0.0d, 0.0d, GridBagConstraints.CENTER, GridBagConstraints.NONE);
        bannerPanel.add(Box.createHorizontalStrut(12), UI_CONSTRAINT);
    }

    protected static void constrain(int x, int y, int w, int h, double wx, double wy, int a, int f) {

        UI_CONSTRAINT.gridx = x;
        UI_CONSTRAINT.gridy = y;
        UI_CONSTRAINT.gridwidth = w;
        UI_CONSTRAINT.gridheight = h;
        UI_CONSTRAINT.weightx = wx;
        UI_CONSTRAINT.weighty = wy;
        UI_CONSTRAINT.anchor = a;
        UI_CONSTRAINT.fill = f;
    }

    private static class WizardContextImpl extends WizardContext {

        WizardRunner reference = null;

        WizardContextImpl(SwingEventManager eventManager, WizardRunner reference) {

            super(eventManager);
            this.reference = reference;
        }

        @Override
        public void showErrorDialog(Throwable error, String message) {

            super.showErrorDialog(reference, error, message);
        }

        @Override
        public void showInformationDialog(String message) {

            super.showInformationDialog(reference, message);
        }

        @Override
        public void updateSteps(Collection<Step> newSteps) {

            Step currentStep = reference.currentStep;
            DefaultListModel listModel = reference.steplistModel;

            int currentIndex = listModel.indexOf(currentStep);
            try {
                int fromIndex = currentIndex + 1;
                int toIndex = listModel.getSize() - 1;
                if (fromIndex <= toIndex) {
                    listModel.removeRange(fromIndex, toIndex);
                }
            } catch (ArrayIndexOutOfBoundsException ignored) {
                debug("", ignored);
            } catch (IllegalArgumentException ignored) {
                debug("", ignored);
            }
            for (Step step : newSteps) {
                listModel.addElement(step);
            }
            reference.pack();
        }

        @Override
        public void invokeNext() {

            reference.nextButton.doClick(0);
        }
    }

    private static class StepListCellRenderer extends DefaultListCellRenderer {

        private static final long serialVersionUID = 1664986318928860084L;
        static final Color selectedFocusedColor = Color.decode("#3D80DF");

        @Override
        public Component getListCellRendererComponent(JList list, Object value, int index, boolean selected,
                boolean hasFocus) {

            super.getListCellRendererComponent(list, value, index, selected, hasFocus);
            if (selected) {
                setBackground(selectedFocusedColor);
                setForeground(Color.WHITE);
            }
            if (index < list.getSelectedIndex()) {
                setIcon(SwingUtilities.loadIconResource("button_ok", 16));
            } else {
                setIcon(null);
            }
            Step step = (Step) value;

            setText(step.getTitle());
            setToolTipText(step.getComment());
            setEnabled(true);
            return this;
        }
    }
}